using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using MongoDB.Bson;
using MongoDB.Driver;

namespace Charisma.MongoDB.Linq
{
    internal static class LinqHelper
    {
        public static readonly MethodInfo EnumerbleSelectMethod = typeof(Enumerable)
            .GetMethods()
            .Where(m => m.Name == "Select")
            .Select(m => new {m, Parameters = m.GetParameters()})
            .Where(x => x.Parameters.Length == 2 && x.Parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>))
            .Select(x => x.m)
            .First();

        public static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        private static readonly bool[] primitiveTypeMap = new[]
            {
                false,
                false,
                false,
                true,
                true,
                true,
                true,
                true,
                true,
                true,
                true,
                true,
                true,
                true,
                true,
                true,
                true,
                false,
                true
            };

        private static readonly HashSet<Type> typeMap = new HashSet<Type>
            {
                typeof (Guid),
                typeof (TimeSpan),
                typeof (ObjectId),
                typeof (Regex),
                typeof (byte[])
            };

        public static bool IsWellKnown(Type type)
        {
            Contract.Requires(type != null);
            return primitiveTypeMap[(int) Type.GetTypeCode(type)] || typeMap.Contains(type);
        }

        public static byte[] ParseHexString(string s)
        {
            Contract.Requires(s != null);

            byte[] bytes;
            if (!TryParseHexString(s, out bytes))
                throw new FormatException("Not a valid hex string");
            return bytes;
        }

        public static string ToHexString(byte[] bytes)
        {
            Contract.Requires(bytes != null);

            return ToHexString(bytes, 0, bytes.Length);
        }

        public static string ToHexString(byte[] bytes, int offset, int length)
        {
            Contract.Requires(bytes != null);
            Contract.Requires(offset >= 0);
            Contract.Requires(length >= 0);
            Contract.Requires(offset + length <= bytes.Length);

            var sb = new StringBuilder(length*2);
            for (int i = offset, j = 0; i < bytes.Length && j < length; i++, j++)
            {
                byte b = bytes[i];
                sb.AppendFormat("{0:x2}", b);
            }
            return sb.ToString();
        }

        public static bool TryParseHexString(string s, out byte[] bytes)
        {
            Contract.Requires(!String.IsNullOrEmpty(s));

            if ((s.Length & 1) != 0)
            {
                s = "0" + s;
            }
            bytes = new byte[s.Length/2];
            for (int i = 0; i < bytes.Length; i++)
            {
                string hex = s.Substring(2*i, 2);
                try
                {
                    byte b = Convert.ToByte(hex, 16);
                    bytes[i] = b;
                }
                catch (FormatException)
                {
                    bytes = null;
                    return false;
                }
            }
            return true;
        }
    }
}